#pragma once

#include <opencv2\opencv.hpp>

#include "rdp.hpp"

#include "ransacline.hpp"

#include "fiocpp.hpp"
#include "fiowin.hpp"

#define DUMP_RESULT
#define DUMP_RESULT_MASK

class CutPage
{
protected:
	static void FillHoleInside(cv::Mat &mo, const cv::Mat &mi, const std::vector<cv::Point> &outCandidates)
	{
		cv::Mat m3(mi.rows + 2, mi.cols + 2, CV_8UC1, cv::Scalar(0));
		cv::Mat m33(mi.rows + 2, mi.cols + 2, CV_8UC1, cv::Scalar(0));

		int connectivity = 4;
		int newMaskVal = 255;
		int flags = connectivity + (newMaskVal << 8) +
			CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;

		for (size_t r = 0; r < outCandidates.size(); r++)
		{
			if (m3.at<uchar>(outCandidates[r].y + 1, outCandidates[r].x + 1) == 0)
			{
				m33.setTo(0);
				int area = cv::floodFill(mi, m33, outCandidates[r], cv::Scalar(255), NULL, cv::Scalar(20), cv::Scalar(20), flags);
				cv::bitwise_or(m3, m33, m3);
			}
		}

		cv::bitwise_not(m3(cv::Rect(1, 1, mi.cols, mi.rows)), mo);
	};

	//given mi is black and white image
	static void FillHoleInside(cv::Mat &mo, const cv::Mat &mi)
	{
		cv::Mat m3(mi.rows + 2, mi.cols + 2, CV_8UC1, cv::Scalar(0));
		mi.copyTo(m3(cv::Rect(1, 1, mi.cols, mi.rows)));

		int connectivity = 4;
		int newMaskVal = 128;
		int flags = connectivity + (newMaskVal << 8) +
			CV_FLOODFILL_FIXED_RANGE;
		//+ CV_FLOODFILL_MASK_ONLY;


		cv::Point seed(0, 0);

		for (seed.x = 0; seed.x < m3.cols; seed.x++)
		{
			if (m3.at<uchar>(seed) == 0)
			{
				int area = cv::floodFill(m3, seed, cv::Scalar(newMaskVal), NULL, cv::Scalar(2), cv::Scalar(2), flags);
			}
		}

		seed.y = m3.rows - 1;
		for (seed.x = 0; seed.x < m3.cols; seed.x++)
		{
			if (m3.at<uchar>(seed) == 0)
			{
				int area = cv::floodFill(m3, seed, cv::Scalar(newMaskVal), NULL, cv::Scalar(2), cv::Scalar(2), flags);
			}
		}

		seed.x = 0;
		for (seed.y = 0; seed.y < m3.rows; seed.y++)
		{
			if (m3.at<uchar>(seed) == 0)
			{
				int area = cv::floodFill(m3, seed, cv::Scalar(newMaskVal), NULL, cv::Scalar(2), cv::Scalar(2), flags);
			}
		}

		seed.x = m3.cols - 1;
		for (seed.y = 0; seed.y < m3.rows; seed.y++)
		{
			if (m3.at<uchar>(seed) == 0)
			{
				int area = cv::floodFill(m3, seed, cv::Scalar(newMaskVal), NULL, cv::Scalar(2), cv::Scalar(2), flags);
			}
		}

		//cv::bitwise_not(m3(cv::Rect(1, 1, mi.cols, mi.rows)), mo);
		mo = (m3(cv::Rect(1, 1, mi.cols, mi.rows)) != newMaskVal);
	};

	static void MaskEdge(const cv::Mat &mask, std::vector<cv::Point> &cnt)
	{
		std::vector<std::vector<cv::Point> > contours;
		std::vector<cv::Vec4i> hierarchy;

		cv::findContours(mask.clone(), contours, hierarchy, CV_RETR_CCOMP, cv::CHAIN_APPROX_NONE);

		if (contours.empty())
		{
			std::cout << "no edge\n";
			return;
		}
		//std::cout << "edge\n" << contours.size() << '\n' << contours[0].size()<<"\n\n";
		cnt.assign(contours[0].begin(), contours[0].end());
	};

	static bool EdgeCorner(std::vector<cv::Point> &cnt, size_t cornidx[4])
	{
		std::vector<size_t> idx;
		bool res = RDP<int>::ApproxClose((int*)cnt.data(), cnt.size(), idx, 4);
		if (res)
		{
			std::rotate(cnt.begin(), cnt.begin() + idx[0], cnt.end());
			for (size_t i = 0; i < idx.size(); i++)
				cornidx[i] = idx[i] - idx[0];
		}
		return res;
	};


	static void GetInliner(std::vector<cv::Point> &ptInline, const std::vector<cv::Point> &pt, double acceptPercentage, double errorThreshold)
	{
		RansacLine rsl;
		rsl.Setup(pt, acceptPercentage, errorThreshold);
		size_t n;
		rsl.Fit(n);
		//rsl.Fit(n, 30000);
		bool *inliner = new bool[pt.size()];
		rsl.GetInOutFlag(inliner);
		for (size_t j = 0; j < pt.size(); j++)
		{
			if (inliner[j])
				ptInline.push_back(pt[j]);
		}
		delete inliner;
	}


	static double AvgDistance(const std::vector<cv::Point> &pts, double la, double lb, double lc)
	{
		double dist = 0;
		for (size_t i = 0; i < pts.size(); i++)
		{
			dist += abs(pts[i].x*la + pts[i].y*lb + lc);
		}
		dist /= sqrt(la*la + lb*lb)*pts.size();

		return dist;
	}

	//fit rectangle
	//line cos(theta)*x + sin(theta)*y + cut[0] = 0 fit to pts[0]
	//line cos(theta)*y - sin(theta)*x + cut[1] = 0 fit to pts[1]
	//line cos(theta)*x + sin(theta)*y + cut[2] = 0 fit to pts[2]
	//line cos(theta)*y - sin(theta)*x + cut[3] = 0 fit to pts[3]

	static void SolveRect(const std::vector<cv::Point> pts[4], double &theta, double cut[4])
	{
		double x[4] = { 0, 0, 0, 0 };
		double y[4] = { 0, 0, 0, 0 };
		double x2[4] = { 0, 0, 0, 0 };
		double y2[4] = { 0, 0, 0, 0 };
		double xy[4] = { 0, 0, 0, 0 };

		for (int k = 0; k < 4; k++)
		{
			for (size_t i = 0; i < pts[k].size(); i++)
			{
				x[k] += pts[k][i].x;
				y[k] += pts[k][i].y;
				x2[k] += pts[k][i].x*pts[k][i].x;
				y2[k] += pts[k][i].y*pts[k][i].y;
				xy[k] += pts[k][i].x*pts[k][i].y;
			}
		}

		double A = 0;
		A += y2[0] - x2[0] + (x[0] * x[0] - y[0] * y[0]) / pts[0].size();
		A += y2[2] - x2[2] + (x[2] * x[2] - y[2] * y[2]) / pts[2].size();
		A += x2[1] - y2[1] + (y[1] * y[1] - x[1] * x[1]) / pts[1].size();
		A += x2[3] - y2[3] + (y[3] * y[3] - x[3] * x[3]) / pts[3].size();

		double B = 0;
		B += xy[0] - x[0] * y[0] / pts[0].size();
		B += xy[2] - x[2] * y[2] / pts[2].size();
		B -= xy[1] - x[1] * y[1] / pts[1].size();
		B -= xy[3] - x[3] * y[3] / pts[3].size();

		std::cout << "A=" << A << ",B=" << B << "\n";

		double theta0[2];
		double cut0[2][4];
		double dis[2] = { 0, 0 };

		theta0[0] = (atan2(2 * B, -A) - CV_PI) / 2;
		cut0[0][0] = -(cos(theta0[0])*x[0] + sin(theta0[0])*y[0]) / pts[0].size();
		cut0[0][1] = (sin(theta0[0])*x[1] - cos(theta0[0])*y[1]) / pts[1].size();
		cut0[0][2] = -(cos(theta0[0])*x[2] + sin(theta0[0])*y[2]) / pts[2].size();
		cut0[0][3] = (sin(theta0[0])*x[3] - cos(theta0[0])*y[3]) / pts[3].size();
		dis[0] += AvgDistance(pts[0], cos(theta0[0]), sin(theta0[0]), cut0[0][0])*pts[0].size();
		dis[0] += AvgDistance(pts[1], -sin(theta0[0]), cos(theta0[0]), cut0[0][1])*pts[1].size();
		dis[0] += AvgDistance(pts[2], cos(theta0[0]), sin(theta0[0]), cut0[0][2])*pts[2].size();
		dis[0] += AvgDistance(pts[3], -sin(theta0[0]), cos(theta0[0]), cut0[0][3])*pts[3].size();


		theta0[1] = (atan2(2 * B, -A)) / 2;
		cut0[1][0] = -(cos(theta0[1])*x[0] + sin(theta0[1])*y[0]) / pts[0].size();
		cut0[1][1] = (sin(theta0[1])*x[1] - cos(theta0[1])*y[1]) / pts[1].size();
		cut0[1][2] = -(cos(theta0[1])*x[2] + sin(theta0[1])*y[2]) / pts[2].size();
		cut0[1][3] = (sin(theta0[1])*x[3] - cos(theta0[1])*y[3]) / pts[3].size();
		dis[1] += AvgDistance(pts[0], cos(theta0[1]), sin(theta0[1]), cut0[1][0])*pts[0].size();
		dis[1] += AvgDistance(pts[1], -sin(theta0[1]), cos(theta0[1]), cut0[1][1])*pts[1].size();
		dis[1] += AvgDistance(pts[2], cos(theta0[1]), sin(theta0[1]), cut0[1][2])*pts[2].size();
		dis[1] += AvgDistance(pts[3], -sin(theta0[1]), cos(theta0[1]), cut0[1][3])*pts[3].size();

		if (dis[0] < dis[1])
		{
			theta = theta0[0];
			cut[0] = cut0[0][0];
			cut[1] = cut0[0][1];
			cut[2] = cut0[0][2];
			cut[3] = cut0[0][3];
		}
		else
		{
			theta = theta0[1];
			cut[0] = cut0[1][0];
			cut[1] = cut0[1][1];
			cut[2] = cut0[1][2];
			cut[3] = cut0[1][3];
		}
	}

	static bool MaskRect(const cv::Mat &mask, double &theta, double cut[4], bool keepFarther, size_t contourThreshold = 4000)
	{
		std::vector<cv::Point> contour;
		MaskEdge(mask, contour);

		if (contour.size() < contourThreshold)
			return false;

		size_t cidx[4];
		EdgeCorner(contour, cidx);

		std::vector<cv::Point> pts[4];

		pts[0].assign(contour.begin(), contour.begin() + cidx[1]);
		pts[1].assign(contour.begin() + cidx[1], contour.begin() + cidx[2]);
		pts[2].assign(contour.begin() + cidx[2], contour.begin() + cidx[3]);
		pts[3].assign(contour.begin() + cidx[3], contour.end());

		std::cout << '(' << pts[0].size() << ',' << pts[1].size() << ',' << pts[2].size() << ',' << pts[3].size() << ")\n";

		double ptDistance[4];
		ptDistance[0] = cv::norm(contour[cidx[0]] - contour[cidx[1]]);
		ptDistance[1] = cv::norm(contour[cidx[1]] - contour[cidx[2]]);
		ptDistance[2] = cv::norm(contour[cidx[2]] - contour[cidx[3]]);
		ptDistance[3] = cv::norm(contour[cidx[3]] - contour[cidx[0]]);

		std::cout << cv::Mat(4, 1, CV_64FC1, ptDistance) << "\n";

		size_t maxIndex = std::max_element(ptDistance, ptDistance + 4) - ptDistance;

		std::vector<cv::Point> ptsin[4];
		GetInliner(ptsin[0], pts[maxIndex], 0.4, 2);
		GetInliner(ptsin[1], pts[(maxIndex + 1) % 4], 0.6, 2);
		GetInliner(ptsin[2], pts[(maxIndex + 2) % 4], 0.6, 2);
		GetInliner(ptsin[3], pts[(maxIndex + 3) % 4], 0.6, 2);


		SolveRect(ptsin, theta, cut);

		//edge 2 keep, edge 0 ignore
		if (keepFarther)
		{
			//keep the edge which is farther from origin(image topleft)
			if (fabs(cut[0]) > fabs(cut[2]))
			{
				double temp = cut[0];
				cut[0] = cut[2];
				cut[2] = temp;
			}
			else
			{

			}
		}
		else
		{
			//keep the edge which is near to origin(image topleft)
			if (fabs(cut[0]) > fabs(cut[2]))
			{

			}
			else
			{
				double temp = cut[0];
				cut[0] = cut[2];
				cut[2] = temp;
			}
		}
		std::cout << '(' << theta << ',' << cut[0] << ',' << cut[1] << ',' << cut[2] << ',' << cut[3] << ")\n";
		std::cout << "h=" << fabs(cut[1] - cut[3]) << ", w=" << fabs(cut[0] - cut[2]) << "\n";

		return true;
	}


	static void DrawLine(cv::Mat &m, cv::Vec3d l, int thickness = 1)
	{
		std::vector<cv::Point> ptl;
		//solve intersection with x=0
		cv::Vec3d its = l.cross(cv::Vec3d(1, 0, 0));
		if (its[2] != 0)
		{
			its[0] /= its[2];
			its[1] /= its[2];
			if (its[1] >= 0 && its[1] <= m.rows - 1)
				ptl.push_back(cv::Point(its[0], its[1]));
		}

		//solve intersection with y=0
		its = l.cross(cv::Vec3d(0, 1, 0));
		if (its[2] != 0)
		{
			its[0] /= its[2];
			its[1] /= its[2];
			if (its[0] >= 0 && its[0] <= m.cols - 1)
				ptl.push_back(cv::Point(its[0], its[1]));
		}

		//solve intersection with x=m.cols-1
		its = l.cross(cv::Vec3d(1, 0, 1 - m.cols));
		if (its[2] != 0)
		{
			its[0] /= its[2];
			its[1] /= its[2];
			if (its[1] >= 0 && its[1] <= m.rows - 1)
				ptl.push_back(cv::Point(its[0], its[1]));
		}

		//solve intersection with y=m.rows-1
		its = l.cross(cv::Vec3d(0, 1, 1 - m.rows));
		if (its[2] != 0)
		{
			its[0] /= its[2];
			its[1] /= its[2];
			if (its[0] >= 0 && its[0] <= m.cols - 1)
				ptl.push_back(cv::Point(its[0], its[1]));
		}

		if (ptl.size() == 2)
			cv::line(m, ptl[0], ptl[1], cv::Scalar(255), thickness);

	}

	static void DrawResult(cv::Mat &canvas, const cv::Mat &pageGray, const cv::Mat &mask, double theta, double cut[4], int thickness = 1)
	{
		cv::Mat seg(pageGray.size(), pageGray.type(), cv::Scalar(0));

		DrawLine(seg, cv::Vec3d(cos(theta), sin(theta), cut[0]), thickness);
		DrawLine(seg, cv::Vec3d(-sin(theta), cos(theta), cut[1]), thickness);
		DrawLine(seg, cv::Vec3d(cos(theta), sin(theta), cut[2]), thickness);
		DrawLine(seg, cv::Vec3d(-sin(theta), cos(theta), cut[3]), thickness);

		cv::Mat ml[] = { pageGray, mask, seg };
		cv::merge(ml, 3, canvas);
	}

	static void WarpRect(const cv::Mat &m, cv::Mat &mw, double theta, double cut[4], int w = 3008, int h = 4277)
	{
		double h0 = abs(cut[1] - cut[3]);
		double w0 = w*h0 / h;

		//keep edge 2, ignore edge 0

		if (cut[0] < cut[2])
			cut[0] = cut[2] - w0;
		else
			cut[0] = cut[2] + w0;


		double xl = abs(cut[0] - cut[2]);
		double yl = abs(cut[1] - cut[3]);
		cv::Mat rcm = (cv::Mat_<double>(2, 1) << xl / 2, yl / 2);

		cv::Vec3d l1(cos(theta), sin(theta), (cut[0] + cut[2]) / 2);
		cv::Vec3d l2(-sin(theta), cos(theta), (cut[1] + cut[3]) / 2);
		cv::Vec3d c = l1.cross(l2);
		double cx = c[0] / c[2];
		double cy = c[1] / c[2];

		cv::Mat cm = (cv::Mat_<double>(2, 1) << cx, cy);


		cv::Mat R = (cv::Mat_<double>(2, 2) << cos(-theta), -sin(-theta), sin(-theta), cos(-theta));
		cv::Mat tm = rcm - R*cm;

		cv::Mat am;
		cv::hconcat(R, tm, am);

		cv::Mat scalem(am.size(), am.type(), cv::Scalar(double(h) / h0));
		am = am.mul(scalem);

		cv::warpAffine(m, mw, am, cv::Size(w + 1, h + 1));
	};

	static void WarpRectA(const cv::Mat &m, cv::Mat &mw, double theta, double cut[4])
	{
		double xl = abs(cut[0] - cut[2]);
		double yl = abs(cut[1] - cut[3]);
		cv::Mat rcm = (cv::Mat_<double>(2, 1) << xl / 2, yl / 2);

		cv::Vec3d l1(cos(theta), sin(theta), (cut[0] + cut[2]) / 2);
		cv::Vec3d l2(-sin(theta), cos(theta), (cut[1] + cut[3]) / 2);
		cv::Vec3d c = l1.cross(l2);
		double cx = c[0] / c[2];
		double cy = c[1] / c[2];

		cv::Mat cm = (cv::Mat_<double>(2, 1) << cx, cy);



		cv::Mat R = (cv::Mat_<double>(2, 2) << cos(-theta), -sin(-theta), sin(-theta), cos(-theta));
		cv::Mat tm = rcm - R*cm;

		cv::Mat am;
		cv::hconcat(R, tm, am);

		//cv::Mat scalem(am.size(), am.type(), cv::Scalar(double(h) / h0));
		//am = am.mul(scalem);

		cv::warpAffine(m, mw, am, cv::Size(ceil(xl), ceil(yl)));
	};


	static void PageMask(cv::Mat &mask, const cv::Mat &page, int t1, int t2, int ar = 1, int closeR = 1)
	{
		cv::Mat ema;
		cv::Canny(page, ema, t1, t2, 2 * ar + 1);

#ifdef DUMP_RESULT_MASK
		cv::imwrite("PageMask-1.png", ema);
#endif

		cv::Mat elem = cv::getStructuringElement(cv::MORPH_RECT,
			cv::Size(closeR * 2 + 1, closeR * 2 + 1), cv::Point(closeR, closeR));
		cv::Mat emb;
		cv::morphologyEx(ema, emb,
			CV_MOP_CLOSE
			, elem);

#ifdef DUMP_RESULT_MASK
		cv::imwrite("PageMask-2.png", emb);
#endif

		//std::vector<cv::Point> ptl(page.cols * 2, cv::Point());
		//for (int i = 0; i < page.cols; i++)
		//{
		//	ptl[i * 2] = cv::Point(i, 0);
		//	ptl[i * 2 + 1] = cv::Point(i, page.rows - 1);
		//}

		cv::Mat emc;

		//FillHoleInside(emc, emb, ptl);
		FillHoleInside(emc, emb);

#ifdef DUMP_RESULT_MASK
		cv::imwrite("PageMask-3.png", emc);
#endif

		int connectivity = 4;
		int newMaskVal = 255;
		int flags = connectivity + (newMaskVal << 8) +
			CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;

		cv::Mat m3(page.rows + 2, page.cols + 2, CV_8UC1, cv::Scalar(0));
		int area = cv::floodFill(emc, m3, cv::Point(page.cols / 2, page.rows / 2), cv::Scalar(255), NULL, cv::Scalar(20), cv::Scalar(20), flags);
		m3(cv::Rect(1, 1, page.cols, page.rows)).copyTo(mask);

#ifdef DUMP_RESULT_MASK
		cv::Mat mo;
		cv::Mat ml[] = { mask, page, mask };
		cv::merge(ml, 3, mo);
		cv::imwrite("PageMask-4.png", mo);
#endif
	};


	static bool Cut(
#ifdef DUMP_RESULT
		cv::Mat &maskrect,
#endif
		cv::Mat &pageCut, const cv::Mat &page, cv::Size sizeCut = cv::Size())
	{
		cv::Mat pageGray;
		if (page.channels() != 1)
			cv::cvtColor(page, pageGray, CV_BGR2GRAY);
		else
		{
			page.copyTo(pageGray);
		}

		cv::Mat mask;

		PageMask(mask, pageGray, 4, 11, 1, 3);

		double theta;
		double cut[4];

		bool res = MaskRect(mask, theta, cut, false, 4000);

		if (!res) return false;

#ifdef DUMP_RESULT
		DrawResult(maskrect, pageGray, mask, theta, cut, 3);
#endif
		if (sizeCut.area() > 0)
			WarpRect(page, pageCut, theta, cut, sizeCut.width, sizeCut.height);
		else
			WarpRectA(page, pageCut, theta, cut);

		return true;
	}



	static bool Cut(std::string path, std::string pathOut,
		cv::Size sizeCut = cv::Size(), bool dumpResult = true, bool dumpStep = true,
		int t1 = 4, int t2 = 11, int ar = 1, int closeR = 1,
		bool keepFarther = false, size_t contourThreshold = 4000, int thickness = 3, int compress=3)
	{
		cv::Mat page = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		if (page.empty())
		{
			std::cout << "fail load image: " << path << "\n";
			return false;
		}

		std::string name = pathOut.substr(0, pathOut.size() - 4);
		std::string ext = pathOut.substr(pathOut.size() - 4, 4);

		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(compress);

		std::vector<int> compression_params_jpg;
		compression_params_jpg.push_back(CV_IMWRITE_JPEG_QUALITY);
		compression_params_jpg.push_back(20);

		cv::Mat pageGray;
		if (page.channels() != 1)
			cv::cvtColor(page, pageGray, CV_BGR2GRAY);
		else
		{
			page.copyTo(pageGray);
		}


		cv::Mat ema;
		cv::Canny(pageGray, ema, t1, t2, 2 * ar + 1);

		if (dumpStep) cv::imwrite(name + "-step-1.png", ema, compression_params);

		cv::Mat elem = cv::getStructuringElement(cv::MORPH_RECT,
			cv::Size(closeR * 2 + 1, closeR * 2 + 1), cv::Point(closeR, closeR));
		cv::Mat emb;
		cv::morphologyEx(ema, emb,
			CV_MOP_CLOSE
			, elem);


		if (dumpStep) cv::imwrite(name + "-step-2.png", emb, compression_params);

		cv::Mat emc;

		FillHoleInside(emc, emb);

		if (dumpStep) cv::imwrite(name + "-step-3.png", emc, compression_params);

		int connectivity = 4;
		int newMaskVal = 255;
		int flags = connectivity + (newMaskVal << 8) +
			CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;

		cv::Mat m3(page.rows + 2, page.cols + 2, CV_8UC1, cv::Scalar(0));
		int area = cv::floodFill(emc, m3, cv::Point(page.cols / 2, page.rows / 2), cv::Scalar(255), NULL, cv::Scalar(20), cv::Scalar(20), flags);
		cv::Mat mask = m3(cv::Rect(1, 1, page.cols, page.rows));

		double theta;
		double cut[4];

		bool res = MaskRect(mask, theta, cut, keepFarther, contourThreshold);

		if (!res)
		{
			std::cout << "fail get rect from mask\n";
			return false;
		}

		if (dumpResult)
		{
			cv::Mat maskrect;
			DrawResult(maskrect, pageGray, mask, theta, cut, thickness);
			cv::imwrite(name + "_MaskRect.jpg", maskrect, compression_params_jpg);
		}

		cv::Mat pageCut;

		if (sizeCut.area() > 0)
			WarpRect(page, pageCut, theta, cut, sizeCut.width, sizeCut.height);
		else
			WarpRectA(page, pageCut, theta, cut);

		res = cv::imwrite(pathOut, pageCut, compression_params);

		if (!res)
		{
			std::cout << "fail save image: " << pathOut << "\n";
			return false;
		}

		return true;
	}


	static void task(
		std::string folderin = "G:\\loser\\2017_01_28\\",
		std::string folderout = "C:\\Users\\h\\Desktop\\png\\",
		std::string extin = ".tif",
		std::string extout = ".png",
		int starti = 16, int endi = 179,
		cv::Size outSize = cv::Size(5999, 8530))
	{
		int i = 147;
		for (i = starti; i <= endi; i++)
		{
			char buf[50];
			sprintf(buf, "IMG_%04d", i);
			std::cout << buf << "\n";
			std::string name(buf);
			Cut(folderin + name + extin, folderout + name + extout, outSize);
		}
	}


	static void CutFromVertex(std::string path, const std::vector<cv::Point> &vertex, int outHeight = 0, int compress = 9)
	{
		cv::Mat page = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		cv::RotatedRect rr = cv::minAreaRect(vertex);
		double rad = -rr.angle*CV_PI / 180;
		cv::Matx22d rm(cos(rad), -sin(rad), sin(rad), cos(rad));
		cv::Vec2d t = cv::Vec2d(rr.size.width / 2, rr.size.height / 2) - rm*cv::Vec2d(rr.center.x, rr.center.y);		
		cv::Matx23d tf2;
		cv::hconcat(rm, t, tf2);
		
		cv::Mat mr;
		if (outHeight > 0)
		{			
			if (rr.size.height > rr.size.width)
			{
				double scale = outHeight / rr.size.width;
				cv::Matx22d sm(scale, 0, 0, scale);
				tf2 = sm*tf2;
				cv::warpAffine(page, mr, tf2, cv::Size(outHeight, rr.size.height*scale), cv::INTER_CUBIC, cv::BORDER_CONSTANT, cv::Scalar::all(0));
			}
			else
			{
				double scale = outHeight / rr.size.height;
				cv::Matx22d sm(scale, 0, 0, scale);
				tf2 = sm*tf2;
				cv::warpAffine(page, mr, tf2, cv::Size(rr.size.width*scale, outHeight), cv::INTER_CUBIC, cv::BORDER_CONSTANT, cv::Scalar::all(0));
			}
		}
		else
			cv::warpAffine(page, mr, tf2, rr.size, cv::INTER_CUBIC, cv::BORDER_CONSTANT, cv::Scalar::all(0));

		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(compress);

		cv::imwrite(path.substr(0, path.size() - 4) + "_Cut.png", mr, compression_params);
	}

public:
	cv::Size sizeCut;
	bool dumpResult;
	bool dumpStep;
	int t1;
	int t2;
	int ar;
	int closeR;
	bool keepFarther;
	size_t contourThreshold;
	int thickness;
	int compress;

public:
	CutPage()
		: sizeCut(5999,8530)
		, dumpResult(false)
		, dumpStep(false)
		, t1(4)
		, t2(11)
		, ar(1)
		, closeR(1)
		, keepFarther(false)
		, contourThreshold(4000)
		, thickness(3)
		, compress(3)
	{

	}


	void Load(const CvFileStorage* fs)
	{
		dumpResult = cvReadIntByName(fs, 0, "dumpResult", dumpResult);

		dumpStep = cvReadIntByName(fs, 0, "dumpStep", dumpStep);

		t1 = cvReadIntByName(fs, 0, "t1", t1);

		t2 = cvReadIntByName(fs, 0, "t2", t2);

		ar = cvReadIntByName(fs, 0, "ar", ar);

		closeR = cvReadIntByName(fs, 0, "closeR", closeR);

		keepFarther = cvReadIntByName(fs, 0, "keepFarther", keepFarther);

		contourThreshold = cvReadIntByName(fs, 0, "contourThreshold", contourThreshold);

		thickness = cvReadIntByName(fs, 0, "thickness", thickness);

		sizeCut.width = cvReadIntByName(fs, 0, "sizeCutwidth", sizeCut.width);

		sizeCut.height = cvReadIntByName(fs, 0, "sizeCutheight", sizeCut.height);

		compress = cvReadIntByName(fs, 0, "compress", compress);
	}

	bool Load(std::string configFile)
	{
		CvFileStorage* fs = cvOpenFileStorage(configFile.c_str(), 0, CV_STORAGE_READ);
		if (fs)
		{
			Load(fs);
			cvReleaseFileStorage(&fs);
			return true;
		}
		std::cout << "fail load " << configFile << "\n";
		return false;
	}


	void Save(CvFileStorage* fs)
	{
		cvWriteInt(fs, "dumpResult", dumpResult);

		cvWriteInt(fs, "dumpStep", dumpStep);

		cvWriteInt(fs, "t1", t1);

		cvWriteInt(fs, "t2", t2);

		cvWriteInt(fs, "ar", ar);

		cvWriteInt(fs, "closeR", closeR);

		cvWriteInt(fs, "keepFarther", keepFarther);

		cvWriteInt(fs, "contourThreshold", contourThreshold);

		cvWriteInt(fs, "thickness", thickness);

		cvWriteInt(fs, "sizeCutwidth", sizeCut.width);

		cvWriteInt(fs, "sizeCutheight", sizeCut.height);

		cvWriteInt(fs, "compress", compress);
	}

	bool Save(std::string configFile)
	{
		CvFileStorage* fs = cvOpenFileStorage(configFile.c_str(), 0, CV_STORAGE_WRITE);
		if (fs)
		{
			Save(fs);
			cvReleaseFileStorage(&fs);
			return true;
		}
		std::cout << "fail save " << configFile << "\n";
		return false;
	}

	bool CutP(std::string path, std::string pathOut)
	{
		return Cut(path, pathOut, sizeCut, dumpResult, dumpStep, t1, t2, ar, closeR, keepFarther, contourThreshold, thickness, compress);
	}

	void CutV(std::string path, std::string vertexFile, int outHeight = 0, int compress = 9)
	{
		std::vector<cv::Point> vtx;
		std::ifstream ifs(vertexFile.c_str());
		if (ifs)
		{
			cv::Point pt;
			while (!ifs.eof())
			{
				if (ifs >> pt.x >> pt.y)
					vtx.push_back(pt);
			}
			ifs.close();			
		}
		
		std::cout << cv::Mat(vtx) << "\n";

		if (vtx.size() > 3)
		{
			CutFromVertex(path, vtx, outHeight, compress);
		}
	}


	static void ScaleHeight(std::string fp, int outH = 8530)
	{
		cv::Mat in = cv::imread(fp, CV_LOAD_IMAGE_UNCHANGED);
		double scale = outH;
		scale /= in.rows;
		cv::Mat out;
		cv::resize(in, out, cv::Size(), scale, scale);
		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(9);
		cv::imwrite(fp, out, compression_params);
	}



	static void Cut4(cv::Mat &pageCut, const cv::Mat &page, cv::Vec2i corner[4], cv::Size outSize)
	{
		std::vector<cv::Point2f> in(4);
		std::vector<cv::Point2f> out(4, cv::Point2f(0, 0));
		for (size_t i = 0; i < in.size(); i++)
		{
			in[i].x = corner[i][0];
			in[i].y = corner[i][1];
		}
		out[1].x = outSize.width - 1;
		out[3].x = outSize.width - 1;
		out[2].y = outSize.height - 1;
		out[3].y = outSize.height - 1;

		cv::Mat H = cv::findHomography(in, out);

		cv::warpPerspective(page, pageCut, H, outSize);
	}

	static void Cut4(cv::Mat &pageCut, const cv::Mat &page, cv::Vec2i corner[4])
	{
		int w[2] = {
			std::ceil(cv::norm(corner[0] - corner[1])),
			std::ceil(cv::norm(corner[2] - corner[3]))
		};

		int h[2] = {
			std::ceil(cv::norm(corner[0] - corner[2])),
			std::ceil(cv::norm(corner[1] - corner[3]))
		};

		cv::Size outsz((w[0] > w[1] ? w[0] : w[1]), (h[0] > h[1] ? h[0] : h[1]));

		Cut4(pageCut, page, corner, outsz);
	}

	static void DrawCorner(cv::Mat &canvas, cv::Vec2i corner[4], int thickness = 3, cv::Scalar color = cv::Scalar(0, 0, 255))
	{
		cv::line(canvas, corner[0], corner[1], color, thickness);
		cv::line(canvas, corner[0], corner[2], color, thickness);
		cv::line(canvas, corner[2], corner[3], color, thickness);
		cv::line(canvas, corner[3], corner[1], color, thickness);
	}
	static void test3(std::string path)
	{
		std::vector<int> compression_params;
		compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
		compression_params.push_back(9);
		cv::Mat page = cv::imread(path, CV_LOAD_IMAGE_UNCHANGED);
		cv::Mat pageCut;
#if 1
		double scale = 8531 / double(page.rows);
		cv::resize(page, pageCut, cv::Size(), scale, scale);
		cv::imwrite(path, pageCut, compression_params);
#else
		cv::Vec2i corner[4] = {
			//cv::Vec2i(423,12746),
			//cv::Vec2i(626,222),
			//cv::Vec2i(8898,12843),
			//cv::Vec2i(9100,371)

			cv::Vec2i(860,19876),
			cv::Vec2i(1378,169),
			cv::Vec2i(9355,20079),
			cv::Vec2i(9874,382)








		};

		Cut4(pageCut, page, corner);

		cv::Mat bgr;
		if (page.channels() == 3)
			bgr = page;
		else
			cv::cvtColor(page, bgr, CV_GRAY2BGR);
		DrawCorner(bgr, corner);
		cv::imwrite("Edge.jpg", bgr);
		cv::imwrite("PageCut.png", pageCut, compression_params);
#endif



}
	

	void RunCut(std::string path, std::string paramPath)
	{		
		Load(paramPath);
		if(path.back()!='/' && path.back() !='\\')
			CutP(path, path.substr(0, path.size() - 4) + "_Cut.png");
		else
		{
			std::vector<std::string> fnl, fol;
			GetFolderContentList(fnl, fol, path);
			if (!fnl.empty())
			{
				std::string folderOut = path + TimeString() + "/";
				SetFolder(folderOut);
				for (size_t i = 0; i < fnl.size(); i++)
				{
					std::cout << i << "/" << fnl.size() << "\n";
					std::string path0 = path + fnl[i];
					std::string path1 = folderOut + fnl[i].substr(0, fnl[i].size() - 4) + "_Cut.png";
					CutP(path0, path1);
				}
			}
		}
	}

};